#include "direct3d9.h"
#include "extern.h"
#include "extern32.h"

#pragma comment(lib,"d3d9.lib")

D3D9App* g_pD3D9App = D3D9App::interface_get();

static const int VERTEX_BUFFER_SIZE = 4000;
static const int  D3DFVF_HGEVERTEX = (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1);

static void D3DXMatrixIdentity(D3DMATRIX* pOut) {
    // XMMatrixIdentity
    pOut->m[0][1] = pOut->m[0][2] = pOut->m[0][3] =
        pOut->m[1][0] = pOut->m[1][2] = pOut->m[1][3] =
        pOut->m[2][0] = pOut->m[2][1] = pOut->m[2][3] =
        pOut->m[3][0] = pOut->m[3][1] = pOut->m[3][2] = 0.0f;

    pOut->m[0][0] = pOut->m[1][1] = pOut->m[2][2] = pOut->m[3][3] = 1.0f;
}


static void D3DXMatrixScaling(D3DMATRIX* _matProj, float ScaleX, float ScaleY, float ScaleZ) {
    // XMMatrixScaling
    auto& M = *_matProj;
    M.m[0][0] = ScaleX;
    M.m[0][1] = 0.0f;
    M.m[0][2] = 0.0f;
    M.m[0][3] = 0.0f;

    M.m[1][0] = 0.0f;
    M.m[1][1] = ScaleY;
    M.m[1][2] = 0.0f;
    M.m[1][3] = 0.0f;

    M.m[2][0] = 0.0f;
    M.m[2][1] = 0.0f;
    M.m[2][2] = ScaleZ;
    M.m[2][3] = 0.0f;

    M.m[3][0] = 0.0f;
    M.m[3][1] = 0.0f;
    M.m[3][2] = 0.0f;
    M.m[3][3] = 1.0f;
}


static void D3DXMatrixTranslation(D3DMATRIX* tmp, float OffsetX, float OffsetY, float OffsetZ) {
    // XMMatrixTranslation
    auto& M = *tmp;
    M.m[0][0] = 1.0f;
    M.m[0][1] = 0.0f;
    M.m[0][2] = 0.0f;
    M.m[0][3] = 0.0f;

    M.m[1][0] = 0.0f;
    M.m[1][1] = 1.0f;
    M.m[1][2] = 0.0f;
    M.m[1][3] = 0.0f;

    M.m[2][0] = 0.0f;
    M.m[2][1] = 0.0f;
    M.m[2][2] = 1.0f;
    M.m[2][3] = 0.0f;

    M.m[3][0] = OffsetX;
    M.m[3][1] = OffsetY;
    M.m[3][2] = OffsetZ;
    M.m[3][3] = 1.0f;
}


static void D3DXMatrixMultiply(D3DMATRIX* tmp, D3DMATRIX* _matProj, D3DMATRIX* _matProj2) {
    // XMMatrixMultiply
    const auto& M1 = *_matProj;
    const auto& M2 = *_matProj2;
    auto& mResult = *tmp;
    // Cache the invariants in registers
    float x = M1.m[0][0];
    float y = M1.m[0][1];
    float z = M1.m[0][2];
    float w = M1.m[0][3];
    // Perform the operation on the first row
    mResult.m[0][0] = (M2.m[0][0] * x) + (M2.m[1][0] * y) + (M2.m[2][0] * z) + (M2.m[3][0] * w);
    mResult.m[0][1] = (M2.m[0][1] * x) + (M2.m[1][1] * y) + (M2.m[2][1] * z) + (M2.m[3][1] * w);
    mResult.m[0][2] = (M2.m[0][2] * x) + (M2.m[1][2] * y) + (M2.m[2][2] * z) + (M2.m[3][2] * w);
    mResult.m[0][3] = (M2.m[0][3] * x) + (M2.m[1][3] * y) + (M2.m[2][3] * z) + (M2.m[3][3] * w);
    // Repeat for all the other rows
    x = M1.m[1][0];
    y = M1.m[1][1];
    z = M1.m[1][2];
    w = M1.m[1][3];
    mResult.m[1][0] = (M2.m[0][0] * x) + (M2.m[1][0] * y) + (M2.m[2][0] * z) + (M2.m[3][0] * w);
    mResult.m[1][1] = (M2.m[0][1] * x) + (M2.m[1][1] * y) + (M2.m[2][1] * z) + (M2.m[3][1] * w);
    mResult.m[1][2] = (M2.m[0][2] * x) + (M2.m[1][2] * y) + (M2.m[2][2] * z) + (M2.m[3][2] * w);
    mResult.m[1][3] = (M2.m[0][3] * x) + (M2.m[1][3] * y) + (M2.m[2][3] * z) + (M2.m[3][3] * w);
    x = M1.m[2][0];
    y = M1.m[2][1];
    z = M1.m[2][2];
    w = M1.m[2][3];
    mResult.m[2][0] = (M2.m[0][0] * x) + (M2.m[1][0] * y) + (M2.m[2][0] * z) + (M2.m[3][0] * w);
    mResult.m[2][1] = (M2.m[0][1] * x) + (M2.m[1][1] * y) + (M2.m[2][1] * z) + (M2.m[3][1] * w);
    mResult.m[2][2] = (M2.m[0][2] * x) + (M2.m[1][2] * y) + (M2.m[2][2] * z) + (M2.m[3][2] * w);
    mResult.m[2][3] = (M2.m[0][3] * x) + (M2.m[1][3] * y) + (M2.m[2][3] * z) + (M2.m[3][3] * w);
    x = M1.m[3][0];
    y = M1.m[3][1];
    z = M1.m[3][2];
    w = M1.m[3][3];
    mResult.m[3][0] = (M2.m[0][0] * x) + (M2.m[1][0] * y) + (M2.m[2][0] * z) + (M2.m[3][0] * w);
    mResult.m[3][1] = (M2.m[0][1] * x) + (M2.m[1][1] * y) + (M2.m[2][1] * z) + (M2.m[3][1] * w);
    mResult.m[3][2] = (M2.m[0][2] * x) + (M2.m[1][2] * y) + (M2.m[2][2] * z) + (M2.m[3][2] * w);
    mResult.m[3][3] = (M2.m[0][3] * x) + (M2.m[1][3] * y) + (M2.m[2][3] * z) + (M2.m[3][3] * w);
}


static void D3DXMatrixOrthoOffCenterLH(D3DMATRIX* tmp, float ViewLeft, float ViewRight, float ViewBottom, float ViewTop, float NearZ, float FarZ) {
    //     assert(!XMScalarNearEqual(ViewRight, ViewLeft, 0.00001f));
    //     assert(!XMScalarNearEqual(ViewTop, ViewBottom, 0.00001f));
    //     assert(!XMScalarNearEqual(FarZ, NearZ, 0.00001f));

    float ReciprocalWidth = 1.0f / (ViewRight - ViewLeft);
    float ReciprocalHeight = 1.0f / (ViewTop - ViewBottom);
    float fRange = 1.0f / (FarZ - NearZ);

    auto& M = *tmp;
    M.m[0][0] = ReciprocalWidth + ReciprocalWidth;
    M.m[0][1] = 0.0f;
    M.m[0][2] = 0.0f;
    M.m[0][3] = 0.0f;

    M.m[1][0] = 0.0f;
    M.m[1][1] = ReciprocalHeight + ReciprocalHeight;
    M.m[1][2] = 0.0f;
    M.m[1][3] = 0.0f;

    M.m[2][0] = 0.0f;
    M.m[2][1] = 0.0f;
    M.m[2][2] = fRange;
    M.m[2][3] = 0.0f;

    M.m[3][0] = -(ViewLeft + ViewRight) * ReciprocalWidth;
    M.m[3][1] = -(ViewTop + ViewBottom) * ReciprocalHeight;
    M.m[3][2] = -fRange * NearZ;
    M.m[3][3] = 1.0f;
}





D3D9App* D3D9App::interface_get() {

	static D3D9App* hge = new D3D9App();
	return hge;
}


// NOLINTNEXTLINE
D3D9App::D3D9App()
	: style_windowed_(0), style_fullscreen_(0), n_prim_(0), cur_prim_type_(0),
	cur_blend_mode_(0), cur_texture_(0), cur_shader_(0){
	hwnd_ = nullptr;

	d3d_ = nullptr;
	d3d_device_ = nullptr;
	d3dpp_ = nullptr;
	targets_ = nullptr;
	cur_target_ = nullptr;
	screen_surf_ = nullptr;
	screen_depth_ = nullptr;
	vertex_buf_ = nullptr;
	index_buf_ = nullptr;
	vert_array_ = nullptr;
	textures_ = nullptr;

	hgefps_ = HGEFPS_VSYNC;

	screen_width_ = 800;
	screen_height_ = 600;
	screen_bpp_ = 32;
	windowed_ = true;
	z_buffer_ = false;
	texture_filter_ = true;
}


void HGE_CALL D3D9App::Gfx_Clear(const hgeColor32 color) {
	if (cur_target_) {
		if (cur_target_->pDepth) {
			d3d_device_->Clear(0, nullptr, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
				color/*.argb*/, 1.0f, 0);
		} else {
			d3d_device_->Clear(0, nullptr, D3DCLEAR_TARGET, color/*.argb*/, 1.0f, 0);
		}
	} else {
		if (z_buffer_) {
			d3d_device_->Clear(0, nullptr, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
				color/*.argb*/, 1.0f, 0);
		} else {
			d3d_device_->Clear(0, nullptr, D3DCLEAR_TARGET, color/*.argb*/, 1.0f, 0);
		}
	}
}

// NOLINTNEXTLINE
bool HGE_CALL D3D9App::Gfx_BeginScene(const HTARGET targ) {
	hgeGAPISurface* p_surf = nullptr;

	D3DDISPLAYMODE Mode;
	auto* target = reinterpret_cast<CRenderTargetList*>(targ);

	const HRESULT hr = d3d_device_->TestCooperativeLevel();
	if (hr == D3DERR_DEVICELOST) {
		return false;
	}
	if (hr == D3DERR_DEVICENOTRESET) {
		if (windowed_) {
			if (FAILED(d3d_->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &Mode))
				|| Mode.Format == D3DFMT_UNKNOWN) {
				post_error("Can't determine desktop video mode");
				return false;
			}

			d3dpp_windowed_.BackBufferFormat = Mode.Format;
			if (format_id(Mode.Format) < 4) {
				screen_bpp_ = 16;
			} else {
				screen_bpp_ = 32;
			}
		}

		if (!gfx_restore()) {
			return false;
		}
	}

	if (vert_array_) {
		post_error("Gfx_BeginScene: Scene is already being rendered");
		return false;
	}

	if (target != cur_target_) {
		if (target) {
			target->pTex->GetSurfaceLevel(0, &p_surf);
		} else {
			p_surf = screen_surf_;
		}
		if (FAILED(d3d_device_->SetRenderTarget(0, p_surf))) {
			if (target) {
				p_surf->Release();
			}
			post_error("Gfx_BeginScene: Can't set render target");
			return false;
		}
		if (target) {
			p_surf->Release();
			if (target->pDepth) {
				d3d_device_->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
			} else {
				d3d_device_->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);
			}
			set_projection_matrix(target->width, target->height);
		} else {
			if (z_buffer_) {
				d3d_device_->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
			} else {
				d3d_device_->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);
			}
			set_projection_matrix(screen_width_, screen_height_);
		}

		d3d_device_->SetTransform(D3DTS_PROJECTION, &proj_matrix_);
		D3DXMatrixIdentity(&view_matrix_);
		d3d_device_->SetTransform(D3DTS_VIEW, &view_matrix_);

		cur_target_ = target;
	}

	d3d_device_->BeginScene();
	vertex_buf_->Lock(0, 0, reinterpret_cast<void**>(&vert_array_), D3DLOCK_DISCARD);
	return true;
}

void HGE_CALL D3D9App::Gfx_EndScene() {
	render_batch(true);
	d3d_device_->EndScene();
	if (!cur_target_) {
		d3d_device_->Present(nullptr, nullptr, nullptr, nullptr);
	}
}

// NOLINTNEXTLINE
void HGE_CALL D3D9App::Gfx_RenderLine(const float x1, const float y1,
	const float x2, const float y2,
	const hgeColor32 color, const float z) {
	if (vert_array_) {
		if (cur_prim_type_ != HGEPRIM_LINES
			|| n_prim_ >= VERTEX_BUFFER_SIZE / HGEPRIM_LINES
			|| cur_texture_ || cur_blend_mode_ != BLEND_DEFAULT) {
			render_batch();

			cur_prim_type_ = HGEPRIM_LINES;
			if (cur_blend_mode_ != BLEND_DEFAULT) {
				set_blend_mode(BLEND_DEFAULT);
			}
			if (cur_texture_) {
				d3d_device_->SetTexture(0, nullptr);
				cur_texture_ = 0;
			}
		}

		const int i = n_prim_ * HGEPRIM_LINES;
		vert_array_[i].x = x1;
		vert_array_[i + 1].x = x2;
		vert_array_[i].y = y1;
		vert_array_[i + 1].y = y2;
		vert_array_[i].z = vert_array_[i + 1].z = z;
		vert_array_[i].col = vert_array_[i + 1].col = color/*.argb*/;
		vert_array_[i].tx = vert_array_[i + 1].tx =
			vert_array_[i].ty = vert_array_[i + 1].ty = 0.0f;

		n_prim_++;
	}
}

void HGE_CALL D3D9App::Gfx_RenderTriple(const hgeTriple* triple) {
	if (vert_array_) {
		if (cur_prim_type_ != HGEPRIM_TRIPLES
			|| n_prim_ >= VERTEX_BUFFER_SIZE / HGEPRIM_TRIPLES
			|| cur_texture_ != triple->tex
			|| cur_blend_mode_ != triple->blend) {
			render_batch();

			cur_prim_type_ = HGEPRIM_TRIPLES;
			if (cur_blend_mode_ != triple->blend) {
				set_blend_mode(triple->blend);
			}
			if (triple->tex != cur_texture_) {
				d3d_device_->SetTexture(0, reinterpret_cast<hgeGAPITexture*>(triple->tex));
				cur_texture_ = triple->tex;
			}
		}

		memcpy(&vert_array_[n_prim_ * HGEPRIM_TRIPLES], triple->v,
			sizeof(hgeVertex) * HGEPRIM_TRIPLES);
		n_prim_++;
	}
}

void HGE_CALL D3D9App::Gfx_RenderQuad(const hgeQuad* quad) {
	if (vert_array_) {
		if (cur_prim_type_ != HGEPRIM_QUADS || n_prim_ >= VERTEX_BUFFER_SIZE / HGEPRIM_QUADS ||
			cur_texture_ != quad->tex
			|| cur_blend_mode_ != quad->blend) {
			render_batch();

			cur_prim_type_ = HGEPRIM_QUADS;
			if (cur_blend_mode_ != quad->blend) {
				set_blend_mode(quad->blend);
			}
			if (quad->tex != cur_texture_) {
				d3d_device_->SetTexture(0, reinterpret_cast<hgeGAPITexture*>(quad->tex));
				cur_texture_ = quad->tex;
			}
		}

		memcpy(&vert_array_[n_prim_ * HGEPRIM_QUADS],
			quad->v,
			sizeof(hgeVertex) * HGEPRIM_QUADS);
		n_prim_++;
	}
}


void HGE_CALL D3D9App::Gfx_RenderQuad(const hgeVertex* vertex, const HTEXTURE tex, hgeBlendMode blend) {
	if (vert_array_) {
		if (cur_prim_type_ != HGEPRIM_QUADS || n_prim_ >= VERTEX_BUFFER_SIZE / HGEPRIM_QUADS ||
			cur_texture_ != /*quad->*/tex
			|| cur_blend_mode_ != /*quad->*/blend) {
			render_batch();

			cur_prim_type_ = HGEPRIM_QUADS;
			if (cur_blend_mode_ != /*quad->*/blend) {
				set_blend_mode(/*quad->*/blend);
			}
			if (/*quad->*/tex != cur_texture_) {
				d3d_device_->SetTexture(0, tex/*reinterpret_cast<hgeGAPITexture*>(quad->tex)*/);
				cur_texture_ = /*quad->*/tex;
			}
		}

		memcpy(&vert_array_[n_prim_ * HGEPRIM_QUADS],
			/*quad->v*/vertex,
			sizeof(hgeVertex) * HGEPRIM_QUADS);
		n_prim_++;
	}
}


D3D9App::HTARGET HGE_CALL D3D9App::Target_Create(int width, int height,
	const bool zbuffer) {
	CRenderTargetList* pTarget;
	D3DSURFACE_DESC TDesc;

	pTarget = new CRenderTargetList;
	pTarget->pTex = nullptr;
	pTarget->pDepth = nullptr;

#ifdef cc_hge
	if (FAILED(D3DXCreateTexture(d3d_device_, width, height, 1, D3DUSAGE_RENDERTARGET,
		d3dpp_->BackBufferFormat, D3DPOOL_DEFAULT, &pTarget->pTex))) {
#else
	if (FAILED(d3d_device_->CreateTexture(width, height, 1, D3DUSAGE_RENDERTARGET,
		d3dpp_->BackBufferFormat, D3DPOOL_DEFAULT, &pTarget->pTex, nullptr))) {
#endif
		post_error("Can't create render target texture");
		delete pTarget;
		return 0;
	}

	pTarget->pTex->GetLevelDesc(0, &TDesc);
	pTarget->width = TDesc.Width;
	pTarget->height = TDesc.Height;

	if (zbuffer) {
		if (FAILED(d3d_device_->CreateDepthStencilSurface(width, height,
			D3DFMT_D16, D3DMULTISAMPLE_NONE, 0, false,
			&pTarget->pDepth, nullptr))) {
			pTarget->pTex->Release();
			post_error("Can't create render target depth buffer");
			delete pTarget;
			return 0;
		}
	}

	pTarget->next = targets_;
	targets_ = pTarget;

	return reinterpret_cast<HTARGET>(pTarget);
}

void HGE_CALL D3D9App::Target_Free(const HTARGET target) {
	auto p_target = targets_;
	CRenderTargetList* p_prev_target = nullptr;

	while (p_target) {
		if (reinterpret_cast<CRenderTargetList*>(target) == p_target) {
			if (p_prev_target) {
				p_prev_target->next = p_target->next;
			} else {
				targets_ = p_target->next;
			}

			if (p_target->pTex) {
				p_target->pTex->Release();
			}
			if (p_target->pDepth) {
				p_target->pDepth->Release();
			}

			delete p_target;
			return;
		}

		p_prev_target = p_target;
		p_target = p_target->next;
	}
}

D3D9App::HTEXTURE HGE_CALL D3D9App::Target_GetTexture(const HTARGET target) {
	auto targ = reinterpret_cast<CRenderTargetList*>(target);
	if (target) {
		return reinterpret_cast<HTEXTURE>(targ->pTex);
	}
	return 0;
}


D3D9App::HTEXTURE HGE_CALL D3D9App::Texture_Create(int width, int height) {
	hgeGAPITexture* p_tex;

#ifdef cc_hge
	if (FAILED(D3DXCreateTexture(d3d_device_, width, height,
		1, // Mip levels
		0, // Usage
		D3DFMT_A8R8G8B8, // Format
		D3DPOOL_MANAGED, // Memory pool
		&p_tex))) {
#else
	if (FAILED(d3d_device_->CreateTexture(width, height,
		1, // Mip levels
		0, // Usage
		D3DFMT_A8R8G8B8, // Format
		D3DPOOL_MANAGED, // Memory pool
		&p_tex, nullptr))) {
#endif
		post_error("Can't create texture");
		return 0;
	}

	return reinterpret_cast<HTEXTURE>(p_tex);
}


void HGE_CALL D3D9App::Texture_Free(const HTEXTURE tex) {
	auto p_tex = reinterpret_cast<hgeGAPITexture*>(tex);
	auto tex_item = textures_;
	CTextureList* tex_prev = nullptr;

	while (tex_item) {
		if (tex_item->tex == tex) {
			if (tex_prev) {
				tex_prev->next = tex_item->next;
			} else {
				textures_ = tex_item->next;
			}
			delete tex_item;
			break;
		}
		tex_prev = tex_item;
		tex_item = tex_item->next;
	}
	if (p_tex != nullptr) {
		p_tex->Release();
	}
}

// NOLINTNEXTLINE
int HGE_CALL D3D9App::Texture_GetWidth(const HTEXTURE tex,
	const bool b_original) {
	D3DSURFACE_DESC TDesc;
	auto p_tex = reinterpret_cast<hgeGAPITexture*>(tex);
	auto tex_item = textures_;

	if (b_original) {
		while (tex_item) {
			if (tex_item->tex == tex) {
				return tex_item->width;
			}
			tex_item = tex_item->next;
		}
		return 0;
	}
	if (FAILED(p_tex->GetLevelDesc(0, &TDesc))) {
		return 0;
	}
	return TDesc.Width;
}


// NOLINTNEXTLINE
int HGE_CALL D3D9App::Texture_GetHeight(const HTEXTURE tex,
	const bool bOriginal) {
	D3DSURFACE_DESC t_desc;
	auto p_tex = reinterpret_cast<hgeGAPITexture*>(tex);
	auto tex_item = textures_;

	if (bOriginal) {
		while (tex_item) {
			if (tex_item->tex == tex) {
				return tex_item->height;
			}
			tex_item = tex_item->next;
		}
		return 0;
	}
	if (FAILED(p_tex->GetLevelDesc(0, &t_desc))) {
		return 0;
	}
	return t_desc.Height;
}


// NOLINTNEXTLINE
uint32_t* HGE_CALL D3D9App::Texture_Lock(const HTEXTURE tex,
	const bool b_read_only,
	const int left, const int top,
	const int width, const int height) {
	auto pTex = reinterpret_cast<hgeGAPITexture*>(tex);
	D3DSURFACE_DESC t_desc;
	D3DLOCKED_RECT t_rect;
	RECT region;
	RECT* prec;
	int flags;

	pTex->GetLevelDesc(0, &t_desc);
	if (t_desc.Format != D3DFMT_A8R8G8B8 && t_desc.Format != D3DFMT_X8R8G8B8) {
		return nullptr;
	}

	if (width && height) {
		region.left = left;
		region.top = top;
		region.right = left + width;
		region.bottom = top + height;
		prec = &region;
	} else {
		prec = nullptr;
	}

	if (b_read_only) {
		flags = D3DLOCK_READONLY;
	} else {
		flags = 0;
	}

	if (FAILED(pTex->LockRect(0, &t_rect, prec, flags))) {
		post_error("Can't lock texture");
		return nullptr;
	}

	return static_cast<uint32_t*>(t_rect.pBits);
}


void HGE_CALL D3D9App::Texture_Unlock(const HTEXTURE tex) {
	auto p_tex = reinterpret_cast<hgeGAPITexture*>(tex);
	p_tex->UnlockRect(0);
}

//////// Implementation ////////

void D3D9App::render_batch(const bool b_end_scene) {
	if (vert_array_) {
		vertex_buf_->Unlock();

		if (n_prim_) {
			switch (cur_prim_type_) {
			case HGEPRIM_QUADS:
				d3d_device_->DrawIndexedPrimitive(
					D3DPT_TRIANGLELIST, 0, 0, n_prim_ << 2, 0, n_prim_ << 1);
				break;

			case HGEPRIM_TRIPLES:
				d3d_device_->DrawPrimitive(D3DPT_TRIANGLELIST, 0, n_prim_);
				break;

			case HGEPRIM_LINES:
				d3d_device_->DrawPrimitive(D3DPT_LINELIST, 0, n_prim_);
				break;
			}

			n_prim_ = 0;
		}

		if (b_end_scene) {
			vert_array_ = nullptr;
		} else {
			vertex_buf_->Lock(0, 0, reinterpret_cast<void**>(&vert_array_),
				D3DLOCK_DISCARD);
		}
	}
}

void D3D9App::set_blend_mode(const hgeBlendMode blend) {
	auto d = -1;

	if ((blend & BLEND_ALPHABLEND) != (cur_blend_mode_ & BLEND_ALPHABLEND)) {
		if (blend & BLEND_ALPHABLEND) {
			d = D3DBLEND_INVSRCALPHA;
		} else {
			d = D3DBLEND_ONE;
		}
	}

	if ((blend & BLEND_DARKEN) != (cur_blend_mode_ & BLEND_DARKEN)) {
		if (blend & BLEND_DARKEN) {
			d3d_device_->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
			d = D3DBLEND_SRCCOLOR;
		} else {
			d3d_device_->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
			if (blend & BLEND_ALPHABLEND) {
				d = D3DBLEND_INVSRCALPHA;
			} else {
				d = D3DBLEND_ONE;
			}
		}
	}

	if (d != -1) {
		d3d_device_->SetRenderState(D3DRS_DESTBLEND, d);
	}

	if ((blend & BLEND_ZWRITE) != (cur_blend_mode_ & BLEND_ZWRITE)) {
		if (blend & BLEND_ZWRITE) {
			d3d_device_->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);
		} else {
			d3d_device_->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
		}
	}

	if ((blend & BLEND_COLORADD + BLEND_DARKEN) !=
		(cur_blend_mode_ & (BLEND_COLORADD + BLEND_DARKEN))) {
		if (blend & BLEND_COLORADD) {
			d3d_device_->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_ADD);
		} else if (blend & BLEND_DARKEN) {
			d3d_device_->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_BLENDCURRENTALPHA);
		} else {
			d3d_device_->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
		}
	}

	cur_blend_mode_ = blend;
}

void D3D9App::set_projection_matrix(const int width, const int height) {
	D3DMATRIX tmp;
	D3DXMatrixScaling(&proj_matrix_, 1.0f, -1.0f, 1.0f);
	D3DXMatrixTranslation(&tmp, -0.5f, (float)height + 0.5f, 0.0f);
	D3DXMatrixMultiply(&proj_matrix_, &proj_matrix_, &tmp);
	D3DXMatrixOrthoOffCenterLH(&tmp, 0, static_cast<float>(width),
		0, static_cast<float>(height), 0.0f, 1.0f);
	D3DXMatrixMultiply(&proj_matrix_, &proj_matrix_, &tmp);
}

bool D3D9App::gfx_init() {
	static const char* szFormats[] = {
			"UNKNOWN", "R5G6B5", "X1R5G5B5", "A1R5G5B5", "X8R8G8B8", "A8R8G8B8"
	};
	hgeGAPIAdapterIdentifier ad_id;
	D3DDISPLAYMODE disp_mode;
	auto d3dfmt = D3DFMT_UNKNOWN;

	// Init D3D
	d3d_ = Direct3DCreate9(D3D_SDK_VERSION); // D3D_SDK_VERSION
	if (d3d_ == nullptr) {
		post_error("Can't create D3D interface");
		return false;
	}

	// Get adapter info

	d3d_->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &ad_id);
	System_Log("D3D Driver: %s", ad_id.Driver);
	System_Log("Description: %s", ad_id.Description);
	System_Log("Version: %d.%d.%d.%d",
		HIWORD(ad_id.DriverVersion.HighPart),
		LOWORD(ad_id.DriverVersion.HighPart),
		HIWORD(ad_id.DriverVersion.LowPart),
		LOWORD(ad_id.DriverVersion.LowPart));

	// Set up Windowed presentation parameters

	if (FAILED(d3d_->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &disp_mode)) || disp_mode.Format ==
		D3DFMT_UNKNOWN) {
		post_error("Can't determine desktop video mode");
		if (windowed_) {
			return false;
		}
	}

	ZeroMemory(&d3dpp_windowed_, sizeof(d3dpp_windowed_));

	d3dpp_windowed_.BackBufferWidth = screen_width_;
	d3dpp_windowed_.BackBufferHeight = screen_height_;
	d3dpp_windowed_.BackBufferFormat = disp_mode.Format;
	d3dpp_windowed_.BackBufferCount = 1;
	d3dpp_windowed_.MultiSampleType = D3DMULTISAMPLE_NONE;
	d3dpp_windowed_.hDeviceWindow = hwnd_;
	d3dpp_windowed_.Windowed = TRUE;

	if (hgefps_ == HGEFPS_VSYNC) {
		d3dpp_windowed_.SwapEffect = D3DSWAPEFFECT_COPY;
		d3dpp_windowed_.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
	} else {
		d3dpp_windowed_.SwapEffect = D3DSWAPEFFECT_COPY;
		d3dpp_windowed_.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
	}

	if (z_buffer_) {
		d3dpp_windowed_.EnableAutoDepthStencil = TRUE;
		d3dpp_windowed_.AutoDepthStencilFormat = D3DFMT_D16;
	}

	// Set up Full Screen presentation parameters

	const UINT n_modes = d3d_->GetAdapterModeCount(D3DADAPTER_DEFAULT, disp_mode.Format);

	for (UINT i = 0; i < n_modes; i++) {
		d3d_->EnumAdapterModes(D3DADAPTER_DEFAULT, disp_mode.Format, i, &disp_mode);

		if (disp_mode.Width != static_cast<UINT>(screen_width_)
			|| disp_mode.Height != static_cast<UINT>(screen_height_)) {
			continue;
		}
		if (screen_bpp_ == 16 && (format_id(disp_mode.Format) > format_id(D3DFMT_A1R5G5B5))) {
			continue;
		}
		if (format_id(disp_mode.Format) > format_id(d3dfmt)) {
			d3dfmt = disp_mode.Format;
		}
	}

	if (d3dfmt == D3DFMT_UNKNOWN) {
		post_error("Can't find appropriate full screen video mode");
		if (!windowed_) {
			return false;
		}
	}

	ZeroMemory(&d3dpp_fullscreen_, sizeof(d3dpp_fullscreen_));

	d3dpp_fullscreen_.BackBufferWidth = screen_width_;
	d3dpp_fullscreen_.BackBufferHeight = screen_height_;
	d3dpp_fullscreen_.BackBufferFormat = d3dfmt;
	d3dpp_fullscreen_.BackBufferCount = 1;
	d3dpp_fullscreen_.MultiSampleType = D3DMULTISAMPLE_NONE;
	d3dpp_fullscreen_.hDeviceWindow = hwnd_;
	d3dpp_fullscreen_.Windowed = FALSE;

	d3dpp_fullscreen_.SwapEffect = D3DSWAPEFFECT_FLIP;
	d3dpp_fullscreen_.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;

	if (hgefps_ == HGEFPS_VSYNC) {
		d3dpp_fullscreen_.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
	} else {
		d3dpp_fullscreen_.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
	}

	if (z_buffer_) {
		d3dpp_fullscreen_.EnableAutoDepthStencil = TRUE;
		d3dpp_fullscreen_.AutoDepthStencilFormat = D3DFMT_D16;
	}

	d3dpp_ = windowed_ ? &d3dpp_windowed_ : &d3dpp_fullscreen_;

	if (format_id(d3dpp_->BackBufferFormat) < 4) {
		screen_bpp_ = 16;
	} else {
		screen_bpp_ = 32;
	}

	// Create D3D Device
	hgeGAPICaps caps;
	d3d_->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);
	uint32_t vp;
	if ((caps.VertexShaderVersion < D3DVS_VERSION(1, 1))
		|| !(caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)) {
		System_Log("Software Vertex-processing device selected");
		vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
	} else {
		System_Log("Hardware Vertex-processing device selected");
		vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
	}
	if (FAILED(d3d_->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd_, vp, d3dpp_,
		&d3d_device_))) {
		post_error("Can't create D3D device");
		return false;
	}

	adjust_window();

	System_Log("Mode: %d x %d x %s\n", screen_width_, screen_height_,
		szFormats[format_id(d3dfmt)]);

	// Create vertex batch buffer

	vert_array_ = nullptr;
	textures_ = nullptr;

	// Init all stuff that can be lost

	set_projection_matrix(screen_width_, screen_height_);
	D3DXMatrixIdentity(&view_matrix_);

	if (!init_lost()) {
		return false;
	}

	Gfx_Clear(0/*hgeColor32::TRANSPARENT_BLACK()*/);

	return true;
}

int D3D9App::format_id(const D3DFORMAT fmt) {
	switch (fmt) {
	case D3DFMT_R5G6B5:
		return 1;
	case D3DFMT_X1R5G5B5:
		return 2;
	case D3DFMT_A1R5G5B5:
		return 3;
	case D3DFMT_X8R8G8B8:
		return 4;
	case D3DFMT_A8R8G8B8:
		return 5;
	default:
		return 0;
	}
}

void D3D9App::adjust_window() {
	RECT* rc;
	LONG style;

	if (windowed_) {
		rc = &rect_windowed_;
		style = style_windowed_;
	} else {
		rc = &rect_fullscreen_;
		style = style_fullscreen_;
	}
	SetWindowLong(hwnd_, GWL_STYLE, style);

	style = GetWindowLong(hwnd_, GWL_EXSTYLE);
	if (windowed_) {
		SetWindowLong(hwnd_, GWL_EXSTYLE, style & (~WS_EX_TOPMOST));
		SetWindowPos(hwnd_, HWND_NOTOPMOST, rc->left, rc->top, rc->right - rc->left,
			rc->bottom - rc->top,
			SWP_FRAMECHANGED);
	} else {
		SetWindowLong(hwnd_, GWL_EXSTYLE, style | WS_EX_TOPMOST);
		SetWindowPos(hwnd_, HWND_TOPMOST, rc->left, rc->top, rc->right - rc->left,
			rc->bottom - rc->top,
			SWP_FRAMECHANGED);
	}
}


void D3D9App::gfx_done() {
	auto target = targets_;

	while (textures_) {
		Texture_Free(textures_->tex);
	}

	if (screen_surf_) {
		screen_surf_->Release();
		screen_surf_ = nullptr;
	}
	if (screen_depth_) {
		screen_depth_->Release();
		screen_depth_ = nullptr;
	}

	while (target) {
		if (target->pTex) {
			target->pTex->Release();
		}
		if (target->pDepth) {
			target->pDepth->Release();
		}
		const auto next_target = target->next;
		delete target;
		target = next_target;
	}
	targets_ = nullptr;

	if (index_buf_ && d3d_device_) {
		d3d_device_->SetIndices(nullptr);
		index_buf_->Release();
		index_buf_ = nullptr;
	}
	if (vertex_buf_ && d3d_device_) {
		if (vert_array_) {
			vertex_buf_->Unlock();
			vert_array_ = nullptr;
		}
		d3d_device_->SetStreamSource(0, nullptr, 0, sizeof(hgeVertex));
		vertex_buf_->Release();
		vertex_buf_ = nullptr;
	}
	if (d3d_device_) {
		d3d_device_->Release();
		d3d_device_ = nullptr;
	}
	if (d3d_) {
		d3d_->Release();
		d3d_ = nullptr;
	}
}


bool D3D9App::gfx_restore() {
	CRenderTargetList* target = targets_;

	//if(!pD3DDevice) return false;
	//if(pD3DDevice->TestCooperativeLevel() == D3DERR_DEVICELOST) return;

	if (screen_surf_) {
		screen_surf_->Release();
	}
	if (screen_depth_) {
		screen_depth_->Release();
	}

	while (target) {
		if (target->pTex) {
			target->pTex->Release();
		}
		if (target->pDepth) {
			target->pDepth->Release();
		}
		target = target->next;
	}

	if (index_buf_) {
		d3d_device_->SetIndices(nullptr);
		index_buf_->Release();
	}
	if (vertex_buf_) {
		d3d_device_->SetStreamSource(0, nullptr, 0, sizeof(hgeVertex));
		vertex_buf_->Release();
	}

	d3d_device_->Reset(d3dpp_);

	if (!init_lost()) {
		return false;
	}

#ifdef cc_hge
	if (proc_gfx_restore_func_) {
		return proc_gfx_restore_func_();
	}
#endif

	return true;
}


bool D3D9App::init_lost() {
	CRenderTargetList* target = targets_;

	// Store render target

	screen_surf_ = nullptr;
	screen_depth_ = nullptr;

	d3d_device_->GetRenderTarget(0, &screen_surf_);
	d3d_device_->GetDepthStencilSurface(&screen_depth_);

	while (target) {
		if (target->pTex)
#ifdef cc_hge
			D3DXCreateTexture(d3d_device_, target->width, target->height, 1, D3DUSAGE_RENDERTARGET, d3dpp_->BackBufferFormat, D3DPOOL_DEFAULT, &target->pTex);
#else
			d3d_device_->CreateTexture(target->width, target->height, 1, D3DUSAGE_RENDERTARGET, d3dpp_->BackBufferFormat, D3DPOOL_DEFAULT, &target->pTex, nullptr);
#endif
		if (target->pDepth)
			d3d_device_->CreateDepthStencilSurface(
				target->width, target->height, D3DFMT_D16, D3DMULTISAMPLE_NONE,
				0, false, &target->pDepth, nullptr);
		target = target->next;
	}

	// Create Vertex buffer
	if (FAILED(d3d_device_->CreateVertexBuffer(VERTEX_BUFFER_SIZE * sizeof(hgeVertex),
		D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY,
		D3DFVF_HGEVERTEX,
		D3DPOOL_DEFAULT,
		&vertex_buf_,
		nullptr))) {
		post_error("Can't create D3D vertex buffer");
		return false;
	}

	d3d_device_->SetVertexShader(nullptr);
	d3d_device_->SetFVF(D3DFVF_HGEVERTEX);
	d3d_device_->SetStreamSource(0, vertex_buf_, 0, sizeof(hgeVertex));

	// Create and setup Index buffer

	if (FAILED(d3d_device_->CreateIndexBuffer(VERTEX_BUFFER_SIZE * 6 / 4 * sizeof(uint16_t),
		D3DUSAGE_WRITEONLY,
		D3DFMT_INDEX16,
		D3DPOOL_DEFAULT,
		&index_buf_,
		nullptr))) {
		post_error("Can't create D3D index buffer");
		return false;
	}

	uint16_t* pIndices, n = 0;
	if (FAILED(index_buf_->Lock(0, 0, (VOID**)&pIndices, 0))) {
		post_error("Can't lock D3D index buffer");
		return false;
	}

	for (int i = 0; i < VERTEX_BUFFER_SIZE / 4; i++) {
		*pIndices++ = n;
		*pIndices++ = n + 1;
		*pIndices++ = n + 2;
		*pIndices++ = n + 2;
		*pIndices++ = n + 3;
		*pIndices++ = n;
		n += 4;
	}

	index_buf_->Unlock();
	d3d_device_->SetIndices(index_buf_);

	// Set common render states

	//pD3DDevice->SetRenderState( D3DRS_LASTPIXEL, FALSE );
	d3d_device_->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
	d3d_device_->SetRenderState(D3DRS_LIGHTING, FALSE);

	d3d_device_->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
	d3d_device_->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
	d3d_device_->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

	d3d_device_->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
	d3d_device_->SetRenderState(D3DRS_ALPHAREF, 0x01);
	d3d_device_->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);

	d3d_device_->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
	d3d_device_->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
	d3d_device_->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);

	d3d_device_->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
	d3d_device_->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
	d3d_device_->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);

	d3d_device_->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT);
	if (texture_filter_) {
		d3d_device_->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
		d3d_device_->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
	} else {
		d3d_device_->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
		d3d_device_->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
	}

	n_prim_ = 0;
	cur_prim_type_ = HGEPRIM_QUADS;

	cur_blend_mode_ = BLEND_DEFAULT;
	// Reset default DirectX Zbuffer write to false
	if (!z_buffer_) {
		d3d_device_->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
	}

	cur_texture_ = NULL;
	cur_shader_ = NULL;

	d3d_device_->SetTransform(D3DTS_VIEW, &view_matrix_);
	d3d_device_->SetTransform(D3DTS_PROJECTION, &proj_matrix_);

	return true;
}